5.1 How to Implement a HTTP Server, i.e., Web Server

  1. Motivations
    • What is the best way to learn any programming?
    • One good idea is to learn programming through pratical hands-on programming practices.
    • A web server with Node.js can be used for many useful applications that use Node.js. For examples, access to MongoDB, and use of WebSocket for the bidirectional communications. And web services. We need to run server-side JavaScript programs from a web app.
    • Let's develop a web server with Node.js. Is it difficult? Not at all. You just need to learn something more about modules, non-blocking, callback functions, and a bit of the HTTP protocol.
    • This web server will be used later in this course for the development of real-time communication applications with WebSocket.

  2. Learning outcomes
    • Implement a simple general HTTP web server with Node.js
    • Analyze the requirements
    • Understand how the HTTP protocol works
    • Use intensively modules and non-blocking calls
    • How to use Node.js to solve the problem, WebServer, to execute sever-side JavaScript (JS) programs and to retrieve files of other types

  3. How to implement a HTTP server, i.e, web server?
    • Read 'The application stack' in The Node Beginner Book
      • What software modules do you need?
        • index
        • server
        • router
        • [request] handler, and request data handling
        • view logic
        • uploader
      • Does Node.js include a web server?
    • Read 'A basic HTTP server', 'Analyzing our HTTP server', 'Passing functions around', 'How function passing makes our HTTP server work' and 'Event-driven asynchronous callbacks' in The Node Beginner Book
      • A basic HTTP server
        // in server1.js
        const http = require("http");  // reference: Node.js HTTP Module
        const server = http.createServer((request, response) => {
            response.writeHead(200, {"Content-Type": "text/plain"});
            response.write("<h1>Hello World!</h1>");
            response.end();
        });
        server.listen(8080);  // Use your port number instead of 8080.
        
        How to run?    $ node server1.js in the command-line interface
        How to access the web server?    http://cs.tru.ca:8080 or http://198.162.21.132:8080 on a web browser, not https. What do you see? Big 'Hello Wrold!'?
      • Trial 0.1. Let's try to run the above app with your port number and test.
      • What does a server do?
        It waits for HTTP requests from clients, and sends responses back to the clients.
      • What does require() do?
      • What does .listen() do?
        The server listens to a TCP port, 8080 in the above code.
      • What is the meaning of passing a function to .createServer()?
        The callback function is called from the system whenever there is an HTTP request from a client.
      • When the callback function is called, two objects, request and response, are passed. request holds all the information related to the HTTP request sent from a client, and response is used to send data back to the client.
        For more information about request, Node.js IncomingMessage Object For more information about response, Node.js HTTP ServerResponse Object
      • Can you explain how an asynchronous callback, i.e., non-blocking, can be good?
      • What is the meaning of 'asynchronous, single-thread, event-driven execution model'?
        If you don't remember them much, you need to go back to 3.2 and 3.3.
      • How to separate code into multiple files?
    • How can you make your own server module?
      • exports
      • How to change server2.js into a module?
        // server2.js
        const PORT_NO = 8080;  // Use your port number, not 8080
        const http = require('http');
        const start = function() {
            const server = http.createServer(
                (request, response) => {  // request: from the client; response: to the client
                    response.writeHead(200, {"Content-Type": "text/plain"});
                    response.write("<h1>Hello World!</h1>");
                    response.end();
                }
            );
            const.listen(PORT_NO);
        }
        // Something more
        exports.start = start;
        
      • How to use the above server module in index2.js?
        // index.2.js
        const server = require('./server2');  // server2.js; the variable server represents all the exported functions in server2.js
        server.start();  // .start() is the function that is defined in server2.js and exported.
        
        $ node ./index2
    • How to move the code that handles request and response to Router?
      • First router3.js
        const route = function(request, response) {
            response.writeHead(200, {'Content-Type': 'text/plain'});  // Plain text, not html text, will be sent back to the client
            response.write("<h1>Hello World!</h1>");
            response.end();
        }
        exports.route = route;
        
      • How to use the router in the above server module?
        • Requre the router in index3.js, and pass it to the server.
        • Then, in the server, the router can be called.
        • index3.js
          const server = require('./server3');
          const router = require('./router3');
          server.start(router);
          
        • server3.js
          const PORT_NO = 8080;  // Use your port number, not 8080
          const http = require('http');
          const start = function(router) {  // Do we have to require 'router'?
              const server = http.createServer(
                  (request, response) => {  // request: from the client; response: to the client
                      router.route(request, response);
                  }
              );
              server.listen(PORT_NO);
          }
          // Something more
          exports.start = start;
          
    • So far we have these three files. Try them yourself on cs.tru.ca or your computer.
      • index.js
        const server = require('./server');
        const router = require('./router');
        
        server.start(router);
        
      • server.js
        const PORT_NO = 8080;  // Use your port number
        const http = require('http');
        
        const start = function(router) {
            const server = http.createServer(
                (request, response) => {  // request: from the client; response: to the client
                    router.route(request, response);
                }
            );
            server.listen(PORT_NO);
        }
        
        // Something more
        
        exports.start = start;
        
      • router.js
        const route = function(request, response) {
            response.writeHead(200, {'Content-Type': 'text/plain'});  // Plain text, not html text, will be sent back to the client
            response.write("<h1>Hello World!</h1>");
            response.end();
        }
        
        exports.route = route;
        
    • Trial 1. Let's try to run the above app with your port number and test.
    • What do we need to do to implement a real web server that passes html files and client-side JS files back to the client, and that executes server-side JS programs?

  4. The requirements and considerations for TRUWSJS (TRU Web Server with JS):
    • a) Pathname in URL sent from clients
      • http://serveraddress[:portnumber]/pathname[?query]; E.g., http://198.162.21.132:8080/~comp3540test/test.html
      • How to obtain the pathname from request? (Note that pathname obtained from url.parse(request.url).pathname starts with '/'.)
        const url = require("url");  // reference: Node.js URL Module
        
        const route = function (request, response) {
            const pathname = decodeURI(url.parse(request.url).pathname);  // decodeURI(): a JS built-in function
                                                                        // reference: JavaScript decodeURI()
            response.writeHead(200, {"Content-Type": "text/html"});  // text/html, not text/plain, in this example
            response.write("<h1>Hello  World!</h1><br>");
            response.write(pathname + "<br>");
            response.end();
        }
        
        exports.route = route;
        
      • Trial 1.5 Let's try to run the above app (,i.e., index.js, server.js, and router.js that includes the above code,) with your port number and test. E.g., http://198.162.21.132:8080/~comp3540test/test.html

      • There are four cases of pathname. We need to obtain the full pathname of a resource that is indicated by the pathname in request.url, so that TRUWSJS can access the resource.
        1. Case 1. Home directory of an user account and public_html: When the pathname starts with '/~name',
          '~name' is assumed as the home HTML directory of the user 'name' on the server computer system is assumed, and '~name' should be expanded.
          For example, '/~tom/test.html' should be replaced by '/home/students/tom/public_html/test.html'.
          For the exact home directory, you may use the "tilde-expansion" module. After you install the module as $ npm install tilde-expansion in the current working directory,
          const tilde = require("tilde-expansion");  // reference: tilde-expansion
          tilde(username, function(expanded) {  // E.g., "~tom" -> "/home/students/tom"
              console.log(expanded);
          });
          
        2. Case 2. Document root directory: When the pathname does not start with '/~name',
          the pathname is assumed to exist under '/var/www/html', i.e., '/var/www/html' + pathname.
          For example, '/csclub/test.html' should be replaced by '/var/www/html/csclub/test.html'.
        3. Case 3. Directory: When the pathname ends with '/',
          'index.html' is assumed after the path.
          For example, '/csclub/game/' should be replaced by '/var/www/html/csclub/game/index.html'.
        4. Case 4. Directory or file: [For simplicity,] Directory when the basename of the pathname does not have a file extension; file when the base name has a file extension,
          When a directory is assumed, and '/' is attached and 'index.html' in the directory is used.
          For example, '/csclub/game' should be replaced by '/var/www/html/csclub/game/index.html'.
      • When the above four cases are processed, the basename of a pathname is always a file.
      • Let's assume that the file extension of server-side JS programs is '.sjs', not '.js', to separate client-side JS files.

    • b) Tasks: What do we have to do from now to implement a basic general web server?
      1. We need to know how to deal with the pathname obtained from request.url.
      2. We need to know how to redirect a http connection to another URL.
      3. We need to know how to read a file for html, JS, text, images, ..., and send them back to the requester.
      4. We need to know how to get 'GET' and 'POST' data from request.url.
      5. We need to know how to execute a server-side JS program, i.e., .sjs files.
        • How to pass the GET data and the POST data to .sjs files.
        • How to get data back from .sjs files.
        • Design decision about .sjs files:
          • Then next proceed() should be exported. This function is the starting point in a server-side JS program, like main() in Java.
            // In each .sjs file,
            //   _GET: object for the GET data
            //   _POST: object for the POST data
            //   callback: callback function to pass a message back to the client      
            const proceed = function(_GET, _POST, callback) {
                ...
            }
            exports.proceed = proceed;
            
          • In the above proceed(), the callback function should be used to send any message back to the client.
          • Server-side JS programs that use the "sjs" file extension will be required in TRUWSJS, and .proceed() will be invoked to execuse them. The return message from .proceed() will be sent back to the client.
          • Another idea used in Amazon Clouding System: We may use different design of proceed(), in which the application code is fully responsible how to use request and how to send messages or data back to the client.
            For example of AWS,
            const handler = async function(request, response) {
                ...
            }
            export handler;
            
      6. We need to know how to support the HTTPS protocol.
      7. We need to know how to run your web server as a service.
      8. We need to know how to use multiple threads for load sharing.
      9. ...

  5. Task 1. How to deal with pathname?
    • How to obtain the pathname from request.url?
      const url = require('url');  // reference: Node.js URL Module
      const route = function (request, response)
      {
          const pathname = decodeURI(url.parse(request.url).pathname);  // encodeURI(), decodeURI()
                                                                      // What does decodeURI() do?
          ...
      }
      
    • Trial 2. Let's try to update router.js to include the above code and test it with different URLs. E.g., http://198.162.21.132:yourportnumber, http://198.162.21.132:yourportnumber/test.html, http://198.162.21.132:yourportnumber/~youraccount/Exercise 1.html. You need to send out the pathname back to the client.

    • As we discussed in the above, 4 cases to get the full path for a target resourse:
      1. Case 1. Home directory of an user account and public_html: When the pathname starts with '/~username'
      2. Case 2. Document root directory: When the pathname does not start with '/~username'
      3. Case 3. Directory: When the pathname ends with '/'
      4. Case 4. Directory or file: [For simplicity,] Directory when the basename of the pathname does not have a file extension; file when the base name has a file extension

    • Cases 1 and 2: How to expand '/~username' or how to add /var/www/html?
      • You need to use another module.
        $ npm install tilde-expansion
        
      • Here is an example how to use the above module.
        const tilde = require('tilde-expansion');  // reference: tilde-expansion
        const words = pathname.split('/'); // words[0] is '' and words[1] is '~...' because pathname starts with '/'. E.g, /~tom/...
        if (words[1] != undefined && words[???][???] == '~')  // e.g., pathname is /~tom/...
            tilde(words[1], function(expanded) {
                let new_pathname = expanded + '/???';  // 'public_html' should be included.
                for (let i = 2; i < words.length; i++)
                    new_pathname += '/' + ???;
            });
        else 
            let new_pathname = "/var/www/html" + pathname;
        
    • Trial 3. Let's try to update router.js to include the above code and test it with different URLs. E.g., http://198.162.21.132:yourportnumber, http://198.162.21.132:yourportnumber/test.html, http://198.162.21.132:yourportnumber/~youraccount/test.html. You need to send out the resolved pathname back to the client.
      const url = require('url');
      
      const route = function (request, response)
      {
          let pathname = decodeURI(url.parse(request.url).pathname);
          const tilde = require('tilde-expansion');
          let words = pathname.split('/'); // words[0] is '' and words[1] is '~...' because pathname starts with '/'.
          // Case 1
          if (words[???] != undefined && words[???][???] == '~') {  // e.g., /~tom/... => '', '~tom', ...
              tilde(words[1], function(expanded) {  // Note that a callback function is used.
                  let new_pathname = expanded + '/???';  // 'public_html' should be included.
                  for (let i = 2; i < words.length; i++)
                      new_pathname += '/' + ???;
                  ????
              });
              ????  // really necessary?
          }
          // Case 2
          else {
              let new_pathname = "/var/www/html" + pathname;
              ????
          }
      }
      
      const proceed_with_resolved_pathname = function (req, res, pathname) {
          ????  // send the pathname to the client
      }
      

    • Case 3: How to see if the pathname ends with '/'?
      • Refer JavaScript String Reference.
      • What method do you think you need to use?
      • Here is an example.
        if (pathname.endsWith("/")) {
            pathname = pathname + "/index.html";
            ...
        }
        
    • Trial 4. Let's try to update router.js to include the above code and test it with different URLs. E.g., cs.tru.ca:8080, cs.tru.ca:8080/test.html, cs.tru.ca/~youraccount/Test/. You need to send out the resolved pathname back to the client.
      const url = require('url');
      
      function route(request, response)
      {
          let pathname = decodeURI(url.parse(request.url).pathname);
          let tilde = require('tilde-expansion');
          let words = pathname.split('/'); // words[0] is '' and words[1] is '~...' because pathname starts with '/'.
          // Case 1
          if (words[1] != undefined && words[1][0] == '~') {  // e.g., /~tom/...
              tilde(words[1], function(expanded) {  // Note that a callback function is used.
                  let new_pathname = expanded + '/???';  // 'public_html' should be included.
                  for (let i = 2; i < words.length; i++)
                      new_pathname += '/' + words[i];
                  proceed_with_resolved_pathname(request, response, new_pathname);
              });
          }
          // Case 2
          else {
              let new_pathname = "/var/www/html" + pathname;
              proceed_with_resolved_pathname(request, response, new_pathname);
          }
      }
      
      function proceed_with_resolved_pathname(req, res, pathname) 
      {
          // Case 3
          ????  // what if pathname ends with '/'?
          
          res.writeHead(200, {"Content-Type": "text/html"});  // text/html, not text/plain, in this example
          res.write("<h1>Hello  World!</h1><br>");
          res.write(??? + "<br>");
          res.end();
      }
      

    • Case 4: How to check if a path includes a file or a directory? How to obtain a file extension?
      • Refer JavaScript String Reference.
      • What methods do you think you need to use?
      • Or you may use 'path' module in Node.js. It could be simpler.
      • Here is an example.
        const path = require("path");  // reference: Node.js Path Module
        
        let fileordirname = path.basename(pathname);
        let words = fileordirname.split(".");
        if (words.length > 1) {
            let extension = words[words.length - 1];
            ...
        } else {
            ...
        }
        
      • If there is no file extension, we assume basename is a directory, and '/index.html' is attached. See 4.2.4.
        const path = require("path");
        
        let fileordirname = path.basename(pathname);
        let words = fileordirname.split(".");
        let extension;
        if (words.length > 1) {
            extension = words[words.length - 1];
        } else {
            pathname = ????
            extension = ????
        }
        ...
        
    • Trial 5. Let's try to update router.js to include the above code and test it with different URLs. E.g., cs.tru.ca:8080, cs.tru.ca:8080/test.sjs, cs.tru.ca/~youraccount/Test. You need to send out the file extension to the client.
      const url = require('url');
      
      function route(request, response)
      {
          let pathname = decodeURI(url.parse(request.url).pathname);
          let tilde = require('tilde-expansion');
          let words = pathname.split('/'); // words[0] is '' and words[1] is '~...' because pathname starts with '/'.
          // Case 1
          if (words[1] != undefined && words[1][0] == '~') {  // e.g., /~tom/...
              tilde(words[1], function(expanded) {  // Note that a callback function is used.
                  let new_pathname = expanded + '/???';  // 'public_html' should be included.
                  for (let i = 2; i < words.length; i++)
                      new_pathname += '/' + words[i];
                  proceed_with_resolved_pathname(request, response, new_pathname);
              });
          }
          // Case 2
          else {
              let new_pathname = "/var/www/html" + pathname;
              proceed_with_resolved_pathname(request, response, new_pathname);
          }
      }
      
      function proceed_with_resolved_pathname(req, res, pathname) 
      {
          // Case 3
          if (pathname.endsWith("/"))
              pathname = pathname + "/index.html";
              
          // Case 4
          ????
          
          res.writeHead(200, {"Content-Type": "text/html"});  // text/html, not text/plain, in this example
          res.write("<h1>Hello  World!</h1><br>");
          res.write("Pathname: " + ??? + "<br>");
          res.write("File extension: " + ??? + "<br>");
          res.end();
      }
      

  6. Task 2. How to redirect to another url?
    • Have you ever open 'http://cs.tru.ca'? Try the link and see what happens.
    • The actual path from 'http://cs.tru.ca/' is '/var/www/index.html'.
      <HTML>
          <HEAD>
              <meta HTTP-EQUIV="REFRESH" content="0; url=http://www.tru.ca:80/science/programs/compsci.html">
          </HEAD>
      </HTML>
      
    • You can send similar HTML code back, or
    • The HTTP code 301 could be used as follow. (You may read List of HTTP status codes.)
      response.writeHead(301, {'Location': different_location});
      

  7. Task 3.1 Non-sjs file case: How to deal with a file? How to read a file?
    • Read 'fs.readFile()' in Node.js File System Module
      • Which module is required?
      • The default encoding is utf8.
      • What does this function do?
      • Here is an example.
        const fs = require('fs');
        const filename = 'test.html';
        fs.readFile(filename, 'utf8', function(err, content) {
            if (!err) {
                console.log(content);
            }
            else {
                console.log(err);
            }
        });
        
      • What if a file is very big? .readFile() might not be a good choice. What then? It would be better to read little by little repeatedly.

  8. Task 3.2 Non-sjs file case: How to send the content of a file back to the client?
    • Here is an example of '.html' file in router.js.
      if (extension != 'sjs' && extension != 'php') {  // Let's not send server-side code back.
          const fs = require('fs');  // reference: Node.js File System Module
          if (extension == 'html' || extension == 'htm')
              content_type = 'text/html';
          else if ...
          ...
          fs.readFile(pathname, 'utf8', function(err, content) {
              if (!err) {
                  response.writeHead(200, {'Content-type': content_type});  // 200: OK; important
                  response.write(content);
                  response.end();
              }
              else {
                  response.writeHead(404, {'Content-type': "text/html"});  // 4xx: client error
                  response.end("<html><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><hr></body></html>");
              }
          });
      }
      
    • Different content types are used for different file extensions, i.e., types.
      • js: application/javascript
      • json: application/json
      • xml: application/xml
      • pdf: application/pdf
      • zip: application/zip
      • css: text/css
      • html: text/html
      • bmp: image/bmp
      • jpeg: image/jpeg
      • ...
      • else: text/plain
    • Find many other Internet media types in Internet media type and IANA list of official media types.
    • Trial 6. Let's try to update router.js to include the above code and test it with different URLs. E.g., cs.tru.ca/~youraccount/test.html, and http://198.162.21.132:yourportnumber/~comp3540test/test.html. You need to send out the file content to the client.

  9. Tasks 4 and 5 sjs file case: Let's recall the format of sjs files explained in 4.2.4.b).
    // A server-side JavaScript file, e.g., echo_server.sjs and calculator_server.sjs:
    
    // _GET: object for the GET data
    // _POST: object for the POST data
    // callback: callback function to pass a message back to the client      
    function proceed(_GET, _POST, callback) {
        ...
    }
    exports.proceed = proceed;
    

  10. Task 4. sjs file case: How to get the 'GET' and 'POST' user inputs? You need the user inputs when you run a server-side JavaScript program. Do you remember .sjs?
    • How to know whether 'GET' or 'POST' date are submitted?
      if (extension == 'sjs') {
          if (request.method.toLowerCase() == "get") {
              ...
          }
          else if (request.???? == '???') {
              ...
          }
          ...
      }
      
    • For the case of 'GET' method, you can use another module - Node.js Query String Module.
    • Here are examples for the URI of 'http://test.com/start.html?command=Login&username=foo&password=topsecret'.
      • decodeURI(url.parse(request.url).pathname)'/start.html'
      • decodeURI(url.parse(request.url).query)'command=Login&username=foor&password=topsecret'
      • let _GET = querystring.parse(decodeURI(url.parse(request.url).query)){ command:'Login', username:'foo', password:'topsecret' }
      • _GET['command']'Login'
      • How to convert an object to a string?
        JSON.stringfy()
    • Trial 7. Let's try to update router.js to include the above code and test it with different URLs. E.g., 198.162.21.132:yourportnumber/~youraccount/test.sjs?name=Tom&age=23. You need to send out the query data to the client. (Note that if your Node WebServer is running as a part of assignment, use a different port number for this Trial.)

    • For the case of 'POST' method, you need to use events. Why?
      • 'request' is an Event Emitter object, and it will emit the 'data' event whenever the POST data is received. When there is no more incoming POST data, the 'end' event is emitted.
      • .on() method on 'request' can used to listen to these events.
      • Here is an example how to get the query string from the 'POST' data.
        let query = '';
        let _POST = {};  // not [] ?
        request.on('data', function(chunk) {  // request is an Event Emitter.
            query += chunk;
        });
        request.???('end', function() {  // the end of the data
            if (query != '')
                _POST = ????  // parsing using the 'querystring' module. Check the example in the above 'GET' case.
        });
        
      • The 'POST' query should be parsed in the same way that the 'GET' query is parsed.
      • Let's put the POST query into a variable named _POST.
      • You may study a bit more how to use EventEmitter. It could be useful in your future.
    • Trial 8. Let's try to update router.js to include the above code and test it with different URLs. E.g., cs.tru.ca:yourportnumber/~comp3540test/echo_calculator.html that includes the <form> to send the POST data. You need to send out the query data to the client.

  11. Task 5. Sjs file case: Finally how to execute a server-side JavaScript program, i.e., '.sjs' program in TRUWSJS?
    • Here is an example program. The action in this example program is 'test_sjs.sjs' with the 'POST' method.
    • Do you have any good ideas how the example program works?
      • Aaaaaahhhhha! Mooooooooduuuuuuuuule!
      • It is an one million dollar secret. Can you explain the idea then?
      • Here is an example.
        //---- TRUWSJS ----
        const sjs = ???(pathname);  // 'pathname' is a sjs script file. This file is required as a module. For example, test.sjs.
                                  // We assume that all '.sjs' programs have the function, proceed(), 
                                  //   that is the starting point like main() in Java and C/C++ programs.
                                  // _GET: object for the GET query; _POST: object for the POST query
                                  //   For example, { command:'Login', username:'foo', password:'topsecret' }
        sjs.proceed(_GET, _POST, function(content) {  // callback function to get the result from the sjs program
            ....
            response.???("" + content);
            response.???();
        });
        
        //---- test.sjs ----
        function proceed(_GET, _POST, callback) {  
            let content;
            if (_GET['command'] == 'Login')  // For example
                ...
            ....
            ???(content);  // Don't forget that this function should pass a message back through callback
        }
        ???.??? = proceed;
        
    • Trial 9. Let's try to update router.js to include the above code and test it. E.g., cs.tru.ca:yourportnumber/~youraccount/test.html or 198.162.21.132:8080/~comp3540test/echo_server.html that includes the <form> to send the POST and GET data to test.sjs. You need to excute the server-side JS program to send out the query data back to the client.

    • Issues with SJS programs
      • What if a server-side JavaScript program, i.e., '.sjs' program, has an error?
      • How to unload the .sjs module after sending the html content?
      • What if the callback function in .procced() is called multiple times from the SJS program?
      • What if the callback function in .proceed() is not invoked from the SJS program?

    • Issue 1. What if a server-side JavaScript program, i.e., '.sjs' program, has an error?
      • The '.sjs' program may make your web server program get crashed. This is because the program is executed as a part of the web server. (Note that the '.sjs' programs can access the global variables. How to make the web server be independent of sjs scripts? We will discuss about it later.)
      • What do you have to do for errors?
    • How to handle exceptions?
      • Read all in JavaScript Errors - Throw and Try to Catch.
        • When do you use try-catch?
        • When do you use throw?
        • How do you create a custom error?
        • What does isNaN() do?
        • Can you catch syntax errors as well with try and catch?
          Yes. Sometimes, no? This is another good reason why the web server should be independent of the sjs scripts.
    • How to throw a custom error?
      • Here is an example.
        const divide = function(x, y) {
            if (y === 0)
                // throw new Error({name: 'Divide', message: "Can't divide by zero"});  // or
                throw {name: 'Divide', message: "Can't divide by zero"};
            else
                return x/y;
        };
        
        try {
            let result = divide(4, 2);
                ...
        }
        ??? (e) {
            console.log(e.???);  // Not just e. e.name, e.stack, e.message, ...
        }
        
    • The '.sjs' program may make your web server program get crashed. What do you have to do?
      ??? {
          const sjs = require(pathname);  // We assume all '.sjs' programs have proceed().
          sjs.proceed(_GET, _POST, function(html_content) {
              ....
          });
      } ??? (err) {
          ....
      }
      

    • Issue 2. How to unload the .sjs module after sending the html content? If the .sjs module is not unloaded, the same module will be used again even though there is a change in the .sjs program.
      Issue 3. What if the callback function in .procced() is called multiple times from the SJS program?
      ??? {
          const sjs = require(pathname);  // We assume all '.sjs' programs have proceed().
          let ended = false;
          sjs.proceed(_GET, _POST, function(html_content) {
              if (!ended) {
                  ....  // write(), ..., end()
                  delete require.cache[require.resolve(pathname)];  // in order to unload this .sjs module; 
                                                                    // https://nodejs.org/api/modules.html#requirecache
                  ended = true;  // when the callback function is invoked multiple times from the sjs app, 
                                 // response.end() is invoked again. 
                                 // It causes an error that cannot be caught.
              }
          });
      } catch (err) {
          ....
          ???[???];  // in order to unload this .sjs module
      }
      

    • Issue 4. What if the callback function in .proceed() is not invoked from the SJS program?
      Use of timer

    • Trial 10. Let's try to update router.js to include the above code and test it.

  12. Task 6. How to support HTTPS?
    • Read all in How does HTTPS really work?.
      • What is the responsibility of SSL/TLS?
      • What information is included in a SSL certificate?
      • What does key exchange mean?
      • Can you press the right button on the image of the https://mytru.tru.ca in the address field in your web browser? What kind of information can you find from the certificate?
    • Node.js: HTTPS instead of HTTP.
    • Here is an example.
      const https = require('https');
      const fs = require('fs');
      
      let options = {
        key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  // Key
        cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')  // Certificate
      };
      
      https.???(options, function (req, res) {  // similar to http
        res.writeHead(200);
        res.end("Hello world!\n");
      }).???(8443);
      
    • How to generate key and certificate? At least for test.
      • Read all in pem: Create private keys and certificates with Node.js.
      • Here is an example.
        const https = require('https'),
            pem = require('pem');
        
        pem.createCertificate({days:100, selfSigned:true}, function(err, keys) {
            if (!err)
                https.???({key: keys.serviceKey, cert: keys.certificate}, function(req, res) {
                    res.writeHead(200);
                    res.end("Hello world!\n")
                }).???(8443);
        });
        
      • Here is the example of TRUWSJS.
        const http = require('http');
        const https = require('https');
        const pem = require('pem');
        //const ws_server = require('./ws_server');
        
        function start(route) 
        {
            // HTTP server
            const server = http.???(function(request, response) {
                route(request, response);
            });
            server.???(8080);
        
            // WebSocket server integrated with HTTP server
            // It will be discussed later.
            //ws_server.start(server);  // WebSocket server: ws Node.js module
        
            // HTTPS server
            pem.createCertificate({days:100, selfSigned:true}, function(err, keys) {
                if (!err)
                    https.???({key: keys.serviceKey, cert: keys.certificate}, function(request, response) {
                        ???(???, ???);
                    }).???(8443);
            });
        }
        
        ???.start = start;
        

  13. Task 7. How to run Node.js as a service?
    • We would like to run our TRUWSJS on Ubuntu as a service like 'Apache' so that web pages/applications can be pulled out with the same one port number always, even when the computer system restarts.
    • Read all in Node.js and Forever as a Service; Simple Upstart and Init Scripts for Ubuntu.
      • When do you use 'forever'?
      • What is a startup script?
    • TRUWSJS is now running as a service on cs.tru.ca with the port number 8080. /etc/init/truwsjs.conf and /etc/init.d/truwsjs
    • How to use the 'service' command?

  14. Task 8. Some other topics

  15. References for further information